package com.fafu.mq.mqserver.core;

import com.fafu.mq.common.MqException;

/**
 * 使用这个类来, 来实现交换机的转发规则
 * 同时也借助这个类验证 bindingKey 是否合法
 *
 */

public class Router {

    // bindingKey 的构造规则
    // 1. 数字, 字母, 下划线
    // 2. 使用 . 分割成若干部分
    // 3. 允许存在 * 或者 # 作为通配符, 但是只能作为独立的分段
    public boolean checkBindingKey(String bindingKey){
        if (bindingKey.length() == 0){
            // 空字符串也是合法情况
            // 比如在使用 direct/fanout 交换机, bindingKey 是用不上的

        }
        // 检查字符串不能存在非法字符
        for (int i = 0; i < bindingKey.length(); i++) {
            char ch = bindingKey.charAt(i);
            if (ch >= 'A' && ch <= 'Z'){
                continue;
            }
            if (ch >= 'a' && ch <= 'z'){
                continue;
            }
            if (ch >= '0' && ch <= '9'){
                continue;
            }
            if (ch == '_' || ch == '.' || ch == '#' || ch == '*'){
                continue;
            }
            return false;
        }
        // 检查 * 和 # 是不是独立部分
        // aaa.*.bbb 合法  aaa.b*.bbb 非法
        String[] words = bindingKey.split("\\.");
        for (String word : words){
            // 检查 word 长度 > 1 并且 包含 * 或者 # 就是非法格式
            if (word.length() > 1 && (word.contains("*") || word.contains("#"))){
                return false;
            }
        }
        // 约定一下 通配符的相邻关系 (人为约定的)
        // 为啥这么约定? 因为前三种过于繁琐
        // 1. aaa.#.#.bbb   ==> 非法
        // 1. aaa.#.*.bbb   ==> 非法
        // 1. aaa.*.#.bbb   ==> 非法
        // 1. aaa.*.*.bbb   ==> 非法
        for (int i = 0; i < words.length - 1; i++) {
            if (words[i].equals("#") && (words[i + 1].equals("#") || words[i + 1].equals("*"))){
                return false;
            }
            if (words[i].equals("*") && words[i + 1].equals("#")){
                return false;
            }
        }


        return true;
    }

    // routingKey 的构造规则
    // 1. 数字, 字母, 下划线
    // 2. 使用 . 分割成若干部分
    public boolean checkRoutingKey(String routingKey) {
        if (routingKey.length() == 0){
            // 空字符串也算合法, 因为当使用 fanout 交换机的时候, 用不上 routingKey, 可以视作""
            return true;
        }
        for (int i = 0; i < routingKey.length(); i++) {
            char ch = routingKey.charAt(i);
            if (ch >= 'A' && ch <= 'Z'){
                continue;
            }
            if (ch >= 'a' && ch <= 'z'){
                continue;
            }
            if (ch >= '0' && ch <= '9'){
                continue;
            }
            if (ch == '_' || ch == '.'){
                continue;
            }
            // 该字符不是上述的合法情况
            return false;
        }
        return true;
    }

    // 这个方法用来判定该消息是否可以转发给这个绑定对应的队列
    public boolean route(ExchangeType exchangeType, Binding binding, Message message) throws MqException {
        // 根据不同的 exchangeType 使用不同的判定转发规则
        if (exchangeType == ExchangeType.FANOUT){
            // 该交换机绑定的所有队列都要转发
            return true;
        } else if (exchangeType == ExchangeType.TOPIC){
            return routeTopic(binding,message);
        } else {
          // 其他情况不存在
          throw new MqException("[Router] 交换机类型非法 exchangeType = " + exchangeType);
        }
    }

    private boolean routeTopic(Binding binding, Message message) {
        // 先将这两个 key 进行切分
        String[] bindingTokens = binding.getBindingKey().split("\\.");
        String[] routingTokens = message.getRoutingKey().split("\\.");

        // 引入两个下标同时指向两个数组
        int bindingIndex = 0;
        int routingIndex = 0;

        // 两个如果都没有到底就继续匹配
        while(bindingIndex < bindingTokens.length && routingIndex < routingTokens.length){
            if(bindingTokens[bindingIndex].equals("*")){
                // 第一个如果为 * 就可以吃掉一个字符
                // 两个都到下一个
                bindingIndex++;
                routingIndex++;
            } else if (bindingTokens[bindingIndex].equals("#")){
                // 为 # 查询下一个字符是否存在
                bindingIndex++;
                // 如果已经遍历到头了, 说明就是对得, # 可以替代后面所有的字符
                if (bindingIndex == bindingTokens.length){
                    return true;
                }
                // 没到头, 找一下 routingTokens 是否存在匹配的字符, 如果不存在就返回 -1 直接不匹配
                routingIndex = findNextMatch(routingTokens,routingIndex,bindingTokens[bindingIndex]);
                if (routingIndex == -1){
                    return false;
                }
                // 如果找到了字符就跳过这两个字符继续进行匹配
                routingIndex++;
                bindingIndex++;
            }else {
                // 遇到普通的字符串直接进行比较
                if (bindingTokens[bindingIndex].equals(routingTokens[routingIndex])){
                    bindingIndex++;
                    routingIndex++;
                } else {
                    return false;
                }
            }

        }
        if (bindingIndex == bindingTokens.length && routingIndex == routingTokens.length){
            return true;
        }
        return false;
    }

    private int findNextMatch(String[] routingTokens, int routingIndex, String bindingToken) {
        for (int i = routingIndex; i < routingTokens.length; i++) {
            if (routingTokens[i].equals(bindingToken)){
                return i;
            }
        }
        return -1;
    }
}
